﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using WeiTap.Data.Attributes;

namespace WeiTap.Data.Core
{
    public class ConventionMapper : IMapper
    {
        public Func<IInflector, string, string> InflectColumnName { get; set; }
        public Func<IInflector, string, string> InflectTableName { get; set; }
        public Func<Type, bool> IsPrimaryKeyAutoIncrement { get; set; }
        public Func<ColumnInfo, Type, PropertyInfo, bool> MapColumn { get; set; }
        public Func<TableInfo, Type, bool> MapPrimaryKey { get; set; }
        public Func<TableInfo,Type, bool> MapTable { get; set; }
        public Func<PropertyInfo, Type, Func<object, object>> FromDbConverter { get; set; }
        public Func<PropertyInfo, Func<object, object>> ToDbConverter { get; set; }
        public ConventionMapper() {
            InflectColumnName = (inflect, cn) => cn;
            InflectTableName = (inflect, tn) => tn;
            MapPrimaryKey = (ti, t) =>
              {
                  var primaryKey = t.GetCustomAttributes<PrimaryKeyAttribute>(true).FirstOrDefault();
                  if (primaryKey != null)
                  {
                      ti.PrimaryKey = primaryKey.Name;
                      ti.AutoIncrement = primaryKey.AutoIncrement;
                      return true;
                  }
                  var prop = t.GetProperties().FirstOrDefault(p =>
                  {
                      if (p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase))
                          return true;
                      return false;
                  });
                  if (prop == null) return false;
                  ti.PrimaryKey = InflectColumnName(Inflector.Instance, prop.Name);
                  ti.AutoIncrement = IsPrimaryKeyAutoIncrement(prop.PropertyType);
                  return true;
              };
            MapTable = (ti, t) =>
            {
                var tableName = t.GetCustomAttributes<TableNameAttribute>(true).FirstOrDefault();
                ti.TableName = tableName != null ? tableName.Name : InflectTableName(Inflector.Instance, t.Name);
                MapPrimaryKey(ti, t);
                return true;
            };
            IsPrimaryKeyAutoIncrement = t =>
            {
                if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
                    t = t.GetGenericArguments()[0];
                if (t == typeof(long) || t == typeof(ulong))
                    return true;
                if (t == typeof(int) || t == typeof(uint)) return true;
                if (t == typeof(short) || t == typeof(ushort)) return true;
                return false;
            };
            MapColumn = (ci, t, pi) => {
                var isExplicit = t.GetCustomAttributes<ExplicitColumnsAttribute>(true).Any();
                var column = pi.GetCustomAttributes<ColumnAttribute>(true).FirstOrDefault();
                if (isExplicit && column == null) return false;
                if (pi.GetCustomAttributes<IgnoreAttribute>(true).Any()) return false;
                if (column != null)
                {
                    ci.ColumnName = column.Name ?? InflectColumnName(Inflector.Instance, pi.Name);
                    ci.InsertTemplate = column.InsertTemplate;
                    ci.UpdateTemplate = column.UpdateTemplate;
                }
                else {
                    ci.ColumnName = InflectColumnName(Inflector.Instance, pi.Name);
                }
                return true;

            };
        }
        public ColumnInfo GetColumnInfo(PropertyInfo entityProperty)
        {
            var ci = new ColumnInfo();
            return MapColumn(ci, entityProperty.DeclaringType, entityProperty) ? ci : null;
        }

        public TableInfo GetTableInfo(Type entityType)
        {
            var ti = new TableInfo();
            return MapTable(ti, entityType) ? ti : null;
        }
        public Func<object, object> GetFromDbConverter(PropertyInfo targetProperty, Type sourceType)
        {
            return FromDbConverter != null ? FromDbConverter(targetProperty, sourceType) : null;
        }
        public Func<object, object> GetToDbConverter(PropertyInfo sourceProperty)
        {
            return ToDbConverter != null ? ToDbConverter(sourceProperty) : null;
        }
    }
}
